
package com.massyframework.beanskin.license.signature.ras;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.RSAPrivateKeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;

import org.bouncycastle.util.io.pem.PemObject;
import org.bouncycastle.util.io.pem.PemReader;

import com.massyframework.beanskin.service.crypto.CryptoException;
import com.massyframework.beanskin.service.crypto.RSAService;
import com.massyframework.beanskin.util.IOUtils;

/**
 * {@link BCRSAService} 提供基于pcks#1 sha256withrsa的加解密，签名和验签方法
 */
public final class BCRSAService  implements RSAService {

    private static String SIGN_ALGORITHMS = "SHA256withRSA";
    private static String PKCS8           = "-----BEGIN PUBLIC KEY-----";
    private static String X509CERT        = "-----BEGIN CERTIFICATE-----";
    private final KeyFactory factory;

    /**
     * 构造方法
     * @throws NoSuchProviderException
     * @throws NoSuchAlgorithmException
     */
    public BCRSAService() throws NoSuchProviderException, NoSuchAlgorithmException {
        this.factory = KeyFactory.getInstance("RSA", "BC");
    }

    /**
     * 使用私钥对<code>content</code>进行加密
     *
     * @param content    {@link byte}数组，要加密的内容
     * @param privateKey {@link String}, 私钥
     * @return {@link byte}数组, 密文
     */
    @Override
    public byte[] encryptWithPrivateKey(byte[] content, String privateKey) 
    		throws CryptoException {
    	try {
	        PrivateKey privKey = this.parsePrivateKey(privateKey);
	        RSAPrivateKeySpec space = this.factory.getKeySpec(privKey, RSAPrivateKeySpec.class);
	        int rsaLength = space.getModulus().bitLength() / 8;
	
	        Cipher cipher = Cipher.getInstance("RSA/None/PKCS1Padding", "BC");
	        cipher.init(Cipher.ENCRYPT_MODE, privKey);
	        return this.doEncrypt(content, cipher, rsaLength);
    	}catch(CryptoException e) {
    		throw e;
    	}catch(Exception e) {
    		throw new CryptoException(e.getMessage(), e);
    	}
    }

    /**
     * 使用公钥对<code>content</code>进行加密
     *
     * @param content   {@link byte}数组，要加密的内容
     * @param publicKey {@link String}, 公钥
     * @return {@link byte}数组, 密文
     */
    @Override
    public byte[] encryptWithPublicKey(byte[] content, String publicKey) throws CryptoException{
    	PublicKey pubKey = null;
        try {
        	pubKey=	this.parsePublicKey(publicKey);
	        if (pubKey == null) {
	        	throw new InvalidKeySpecException("not support format: publicKey.");
	        }
        
        
	        RSAPublicKeySpec space = this.factory.getKeySpec(pubKey, RSAPublicKeySpec.class);
	        int rsaLength = space.getModulus().bitLength() / 8;
	
	        Cipher cipher = Cipher.getInstance("RSA/None/PKCS1Padding", "BC");
	        cipher.init(Cipher.ENCRYPT_MODE, pubKey);
	        return this.doEncrypt(content, cipher, rsaLength);
        }catch(CryptoException e) {
    		throw e;
    	}catch(Exception e) {
    		throw new CryptoException(e.getMessage(), e);
    	}
    }

    /**
     * 使用私钥对<code>encrypted</code>进行解密.<br>
     * 本方法和{@link #encryptWithPublicKey(byte[], String)}相对应使用。
     *
     * @param encrypted  {@link byte}数组，加密的内容
     * @param privateKey {@link String}, 私钥
     * @return {@link byte}数组, 解密后的数据
     */
    @Override
    public byte[] decryptWithPrivateKey(byte[] encrypted, String privateKey) throws CryptoException{
    	try {
	        PrivateKey privKey = this.parsePrivateKey(privateKey);
	        RSAPrivateKeySpec space = this.factory.getKeySpec(privKey, RSAPrivateKeySpec.class);
	        int rsaLength = space.getModulus().bitLength() / 8;
	
	        Cipher cipher = Cipher.getInstance("RSA/None/PKCS1Padding", "BC");
	        cipher.init(Cipher.DECRYPT_MODE, privKey);
	        return this.doDecrypt(encrypted, cipher, rsaLength);
    	}catch(CryptoException e) {
    		throw e;
    	}catch(Exception e) {
    		throw new CryptoException(e.getMessage(), e);
    	}
    }

    /**
     * 使用公钥对<code>encrypted</code>进行解密.<br>
     * 本方法和{@link #encryptWithPrivateKey(byte[], String)}相对应使用。
     *
     * @param encrypted {@link byte}数组，加密的内容
     * @param publicKey {@link String}, 私钥
     * @return {@link byte}数组, 解密后的数据
     */
    @Override
    public byte[] decryptWithPublicKey(byte[] encrypted, String publicKey) throws CryptoException{
        PublicKey pubKey = null;
        try {
        	pubKey=	this.parsePublicKey(publicKey);
	        if (pubKey == null) {
	        	throw new InvalidKeySpecException("not support format: publicKey.");
	        }
        
	        RSAPublicKeySpec space = this.factory.getKeySpec(pubKey, RSAPublicKeySpec.class);
	        int rsaLength = space.getModulus().bitLength() / 8;
	
	        Cipher cipher = Cipher.getInstance("RSA/None/PKCS1Padding", "BC");
	        cipher.init(Cipher.DECRYPT_MODE, pubKey);
	        return this.doDecrypt(encrypted, cipher, rsaLength);
	    }catch(CryptoException e) {
			throw e;
		}catch(Exception e) {
			throw new CryptoException(e.getMessage(), e);
		}
    }

    /**
     * 执行加密处理
     * @param content {@link byte}数组，要加密内容
     * @param cipher {@link Cipher}
     * @param rsaLength {@link int}, rsa密钥长度
     * @return {@link byte}数组，加密后的结果
     */
    private byte[] doEncrypt(byte[] content, Cipher cipher, int rsaLength) throws BadPaddingException, IllegalBlockSizeException {
        // 对数据分段
        int inputLen = content.length;
        int maxEncryptBlock = rsaLength -11;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offSet = 0;
        byte[] cache;
        int i = 0;
        while (inputLen - offSet > 0) {
            if (inputLen - offSet > maxEncryptBlock) {
                cache = cipher.doFinal(content, offSet, maxEncryptBlock);
            } else {
                cache = cipher.doFinal(content, offSet, inputLen - offSet);
            }
            out.write(cache, 0, cache.length);
            i++;
            offSet = i * maxEncryptBlock;
        }
        return out.toByteArray();
    }

    /**
     * 执行解密
     * @param encrypted {@link byte}数组, 加密的内容
     * @param cipher {@link Cipher}
     * @param rsaLength {@link int}
     * @return {@link byte}, 加密前的原文
     */
    private byte[] doDecrypt(byte[] encrypted, Cipher cipher, int rsaLength) throws BadPaddingException, IllegalBlockSizeException {
        // 对数据分段解密
        int inputLen = encrypted.length;

        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offSet = 0;
        byte[] cache;
        int i = 0;
        while (inputLen - offSet > 0) {
            if (inputLen - offSet > rsaLength) {
                cache = cipher.doFinal(encrypted, offSet, rsaLength);
            } else {
                cache = cipher.doFinal(encrypted, offSet, inputLen - offSet);
            }
            out.write(cache, 0, cache.length);
            i++;
            offSet = i * rsaLength;
        }
        return out.toByteArray();
    }

    /**
     * 签名
     *
     * @param content    {@link byte}数组，签名的内容
     * @param privateKey {@link String}, 签名的私钥字符串
     * @return {@link String}, 签名生成的数据, Base64编码
     */
    @Override
    public String sign(byte[] content, String privateKey) throws CryptoException {
    	try {
	        PrivateKey privKey = this.parsePrivateKey(privateKey);
	
	        Signature signature = Signature.getInstance(SIGN_ALGORITHMS);
	        signature.initSign(privKey);
	        signature.update(content);
	        byte[] signed = signature.sign();
	        return Base64.getEncoder().encodeToString(signed);
    	}catch(CryptoException e) {
    		throw e;
    	}catch(Exception e) {
    		throw new CryptoException(e.getMessage(), e);
    	}
    }

    /**
     * 验证
     *
     * @param content    {@link byte}数组，签名的内容
     * @param signedData {@link String}， 签名生成的数据, Base64编码
     * @param publicKey  {@link String}, 验证签名的公钥字符串
     * @return {@link boolean}, 签名验证是否成功，返回<code>true</code>表示成功
     * @throws SignatureException 
     * @throws NoSuchProviderException 
     * @throws CertificateException 
     */
    @Override
    public boolean verify(byte[] content, String signedData, String publicKey) throws CryptoException {
    	PublicKey pubKey = null;
        try {
        	pubKey=	this.parsePublicKey(publicKey);
	        if (pubKey == null) {
	        	throw new InvalidKeySpecException("not support format: publicKey.");
	        }
      
	        Signature signature = Signature.getInstance(SIGN_ALGORITHMS);
	        signature.initVerify(pubKey);
	        signature.update(content);
	
	        return signature.verify(Base64.getDecoder().decode(signedData));
	    }catch(CryptoException e) {
			throw e;
		}catch(Exception e) {
			throw new CryptoException(e.getMessage(), e);
		}
    }

    /**
     * 解析私钥
     *
     * @param privatekey {@link String}, 私钥字符串
     * @return {@link PrivateKey}
     */
    protected PrivateKey parsePrivateKey(String privatekey)
            throws IOException {
        StringReader reader = new StringReader(privatekey);
        PemReader pomReader = new PemReader(reader);
        try{
            PemObject pemObject = pomReader.readPemObject();
            return new BCRSAPrivateKey(pemObject.getContent());
        }finally{
            IOUtils.close(pomReader);
        }
    }
    
    /**
     * 解析公钥
     *
     * @param publicKey {@link String}, 公钥字符串
     * @return {@link PublicKey}
     * @throws CertificateException 
     * @throws NoSuchProviderException 
     */
    protected PublicKey parsePublicKey(String publicKey)
            throws IOException, InvalidKeySpecException, CertificateException, NoSuchProviderException {
    	if (publicKey.startsWith(PKCS8)) {
    		StringReader reader = new StringReader(publicKey);
            PemReader pomReader = new PemReader(reader);
            try{
                PemObject pemObject = pomReader.readPemObject();
                X509EncodedKeySpec spec =
                        new X509EncodedKeySpec(pemObject.getContent());
                return factory.generatePublic(spec);
            }finally{
                IOUtils.close(pomReader);
            }
    	}
    	
    	if (publicKey.startsWith(X509CERT)) {
    		CertificateFactory of = CertificateFactory.getInstance("x.509", "BC");
            InputStream in = null;
            try {
                in = new ByteArrayInputStream(publicKey.getBytes());
                java.security.cert.Certificate ceof = of.generateCertificate(in);
                return ceof.getPublicKey();
            }finally{
                IOUtils.close(in);
            }
    	}

        return null;
    }
}
